package com.stable.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.google.common.util.concurrent.ListenableFuture;
import com.stable.constant.RedisConstant;
import com.stable.enums.RunCycleEnum;
import com.stable.enums.RunLogBizTypeEnum;
import com.stable.es.dao.base.EsNewFinanceDao;
import com.stable.job.MyCallable;
import com.stable.spider.eastmoney.EastMoneySpider;
import com.stable.spider.tushare.TushareSpider;
import com.stable.utils.DateUtil;
import com.stable.utils.RedisUtil;
import com.stable.utils.TasksWorker;
import com.stable.vo.NewFinanceAnalysis;
import com.stable.vo.bus.NewFinance;
import com.stable.vo.bus.NewFinanceReturnInfo;
import com.stable.vo.bus.StockBaseInfo;
import com.stable.vo.spi.req.EsQueryPageReq;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 财务报表
 *
 * @author zhouwen
 */
@Service
@Log4j2
public class NewFinanceService {
    @Autowired
    TushareSpider tushareSpider;
    @Autowired
    private EsNewFinanceDao esNewFinanceDao;
    @Autowired
    private EastMoneySpider eastMoneySpider;
    @Autowired
    private RedisUtil redisUtil;

    private final Semaphore semapProBar = new Semaphore(1);

    public ListenableFuture<Object> jobSynStockNewFinance() {
        try {
            semapProBar.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return TasksWorker.getInstance().getService()
                .submit(new MyCallable(RunLogBizTypeEnum.NEW_FINANCE, RunCycleEnum.DAY) {
                    public Object mycall() {
                        try {
                            log.info("财务数据同步[started]");
                            JSONArray array = tushareSpider.getStockCodeList();
                            List collect = new ArrayList();
                            for (int i = 0; i < array.size(); i++) {
                                StockBaseInfo base = new StockBaseInfo(array.getJSONArray(i));
                                // 按报告期财报
                                List<NewFinance> collectRep = eastMoneySpider.getNewFinanceAnalysis(base.getCode(), 0).stream()
                                        .map(x -> transfer(x, base.getCode())).collect(Collectors.toList());
                                // 年度财报
                                List<NewFinance> collectYear = eastMoneySpider.getNewFinanceAnalysis(base.getCode(), 1).stream()
                                        .map(x -> transfer(x, base.getCode())).collect(Collectors.toList());
                                collect.addAll(collectRep);
                                collect.addAll(collectYear);
                                if (collect.size() > 1000 || (i == (array.size() - 1))) {
                                    esNewFinanceDao.saveAll(collect);
                                    log.info("插入财务报表：size:{}", collect.size());
                                    collect.clear();
                                }
                            }
                            log.info("财务数据同步[end]");
                            return null;
                        } finally {
                            semapProBar.release();
                        }
                    }
                });
    }

    // 转换财务数据
    private NewFinance transfer(NewFinanceAnalysis data, String code) {
        NewFinance newFinance = new NewFinance();
        newFinance.setCode(code);
        newFinance.setDate(data.getDate());
        newFinance.setType(data.getType());
        newFinance.setDateMilli(DateUtil.getDateEpochMilli(data.getDate()));
        newFinance.setYyzsrtbzz(data.getYyzsrtbzz().equals("--") ? 0 : Double.valueOf(data.getYyzsrtbzz()));
        newFinance.setGsjlrtbzz(data.getGsjlrtbzz().equals("--") ? 0 : Double.valueOf(data.getGsjlrtbzz()));
        newFinance.setKfjlrtbzz(data.getKfjlrtbzz().equals("--") ? 0 : Double.valueOf(data.getKfjlrtbzz()));
        newFinance.setJqjzcsyl(data.getJqjzcsyl().equals("--") ? 0 : Double.valueOf(data.getJqjzcsyl()));
        newFinance.setMll(data.getMll().equals("--") ? 0 : Double.valueOf(data.getMll()));
        newFinance.setJll(data.getJll().equals("--") ? 0 : Double.valueOf(data.getJll()));
        newFinance.setXsxjlyysr(data.getXsxjlyysr().equals("--") ? 0 : Double.valueOf(data.getXsxjlyysr()));
        newFinance.setZcfzl(data.getZcfzl().equals("--") ? 0 : Double.valueOf(data.getZcfzl()));
        newFinance.setId();
        return newFinance;
    }


    // 查询
    public List<NewFinanceReturnInfo> queryFinance(String queryDate, String code, double last1YyzsrtbzzPercent, double last2YyzsrtbzzPercent, double last3YyzsrtbzzPercent, double last1JqjzcsylPercent, double last2JqjzcsylPercent, double last3JqjzcsylPercent, EsQueryPageReq queryPage, String conceptName, double zcfzl, String gsld) {
        log.info("queryFinance queryDate={},last1YyzsrtbzzPercent={},last2YyzsrtbzzPercent={},last3YyzsrtbzzPercent={},last1JqjzcsylPercent={},last2JqjzcsylPercent={},last3JqjzcsylPercent={},pageable.page={},pageable.size={}, sortName:{} ,sortOrder:{}", queryDate, last1YyzsrtbzzPercent, last2YyzsrtbzzPercent, last3YyzsrtbzzPercent, last1JqjzcsylPercent, last2JqjzcsylPercent, last3JqjzcsylPercent, queryPage.getPageNum(),
                queryPage.getPageSize(), queryPage.getSortName(), queryPage.getSortOrder());
        Map<Object, Object> stockBasicMap = redisUtil.hGetAll(RedisConstant.stock_basic);

        String redisMapKey = "finance_" + queryDate + "_" + last1YyzsrtbzzPercent + "_" + last2YyzsrtbzzPercent + "_" + last3YyzsrtbzzPercent;
        List<NewFinanceReturnInfo> infos = new ArrayList<>();
        Map<Object, Object> objectMap = redisUtil.hGetAll(redisMapKey);
        if (objectMap.size() == 0) {
            jobQueryFinanceToRedis(queryDate, code, last1YyzsrtbzzPercent, last2YyzsrtbzzPercent, last3YyzsrtbzzPercent, last1JqjzcsylPercent, last2JqjzcsylPercent, last3JqjzcsylPercent, queryPage, zcfzl);
            return null;
        } else {
            objectMap.values().stream().forEach(info -> {
                NewFinanceReturnInfo newFinanceReturnInfo = JSON.parseObject((String) info, NewFinanceReturnInfo.class);
                StockBaseInfo stockBaseInfo = JSON.parseObject((String) stockBasicMap.get(newFinanceReturnInfo.getCode()), StockBaseInfo.class);
                if (!Objects.isNull(stockBaseInfo)) {
                    newFinanceReturnInfo.setName(stockBaseInfo.getName());
                    newFinanceReturnInfo.setConceptName(stockBaseInfo.getConcept_name());
                    newFinanceReturnInfo.setGsld(stockBaseInfo.getGsld());
                }
                if (StringUtils.isNotBlank(conceptName)) {
                    if (StringUtils.isNotBlank(newFinanceReturnInfo.getConceptName()) && !newFinanceReturnInfo.getConceptName().contains(conceptName)) return;
                }
                if (StringUtils.isNotBlank(gsld)) {
                    if (StringUtils.isNotBlank(newFinanceReturnInfo.getGsld()) && !newFinanceReturnInfo.getGsld().contains(gsld)) return;
                }
                infos.add(newFinanceReturnInfo);
            });

            if (StringUtils.equals(queryPage.getSortName(), "last1YyzsrtbzzPercent")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast1YyzsrtbzzPercent).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last1Gsjlrgdhbzz")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast1Gsjlrtbzz).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last1Mll")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast1Mll).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last1Jll")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast1Jll).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last1Xsxjlyysr")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast1Xsxjlyysr).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last1JqjzcsylPercent")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast1JqjzcsylPercent).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last2YyzsrtbzzPercent")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast2YyzsrtbzzPercent).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last2Gsjlrgdhbzz")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast2Gsjlrtbzz).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last2Mll")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast2Mll).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last2Jll")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast2Jll).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last2Xsxjlyysr")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast2Xsxjlyysr).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last2JqjzcsylPercent")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast2JqjzcsylPercent).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last3YyzsrtbzzPercent")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast3YyzsrtbzzPercent).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last3Gsjlrgdhbzz")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast3Gsjlrtbzz).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last3Mll")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast3Mll).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last3Jll")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast3Jll).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last3Xsxjlyysr")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast3Xsxjlyysr).reversed());
            } else if (StringUtils.equals(queryPage.getSortName(), "last3JqjzcsylPercent")) {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast3JqjzcsylPercent).reversed());
            } else {
                infos.sort(Comparator.comparing(NewFinanceReturnInfo::getLast1YyzsrtbzzPercent).reversed());
            }
            return infos;
        }
    }


    // 把水平涨跌弧度数据写入redis
    public ListenableFuture<Object> jobQueryFinanceToRedis(String queryDate, String code, double last1YyzsrtbzzPercent, double last2YyzsrtbzzPercent, double last3YyzsrtbzzPercent, double last1JqjzcsylPercent, double last2JqjzcsylPercent, double last3JqjzcsylPercent, EsQueryPageReq queryPage, double zcfzl) {

        if (queryPage.getPageSize() == 0) queryPage.setPageSize(1000); //默认取1只股票3年数据
        log.info("page startDate={},pageable.page={},pageable.size={}, sortName:{} ,sortOrder:{}", queryPage.getPageNum(),
                queryPage.getPageSize(), queryPage.getSortName(), queryPage.getSortOrder());
        return TasksWorker.getInstance().getService()
                .submit(new MyCallable(RunLogBizTypeEnum.THREE_FINANCE_ANALYZE, RunCycleEnum.DAY) {
                    public Object mycall() {
                        return doFinanceMethod(queryDate, code, last1YyzsrtbzzPercent, last2YyzsrtbzzPercent, last3YyzsrtbzzPercent, last1JqjzcsylPercent, last2JqjzcsylPercent, last3JqjzcsylPercent, queryPage, zcfzl);
                    }
                });
    }

    public Object doFinanceMethod(String queryDate, String code, double last1YyzsrtbzzPercent, double last2YyzsrtbzzPercent, double last3YyzsrtbzzPercent, double last1JqjzcsylPercent, double last2JqjzcsylPercent, double last3JqjzcsylPercent, EsQueryPageReq queryPage, double zcfzl) {
        try {
            semapProBar.acquire();
            String redisMapKey = "finance_" + queryDate + "_" + last1YyzsrtbzzPercent + "_" + last2YyzsrtbzzPercent + "_" + last3YyzsrtbzzPercent;

            Map<Object, Object> stockBasicMap = redisUtil.hGetAll(RedisConstant.stock_basic);
            if (StringUtils.isNotBlank(code)) {
                StockBaseInfo stockBaseInfo = JSON.parseObject((String) stockBasicMap.get(code), StockBaseInfo.class);
                if (!Objects.isNull(stockBaseInfo)) {
                    doCodeAnalyMethod(queryDate, code, last1YyzsrtbzzPercent, last2YyzsrtbzzPercent, last3YyzsrtbzzPercent, last1JqjzcsylPercent, last2JqjzcsylPercent, last3JqjzcsylPercent, queryPage, redisMapKey, stockBaseInfo, zcfzl);
                }
            } else {
                stockBasicMap.values().stream().forEach(info -> {
                    StockBaseInfo stockBaseInfo = JSON.parseObject((String) info, StockBaseInfo.class);
                    if (!Objects.isNull(stockBaseInfo)) {
                        doCodeAnalyMethod(queryDate, stockBaseInfo.getCode(), last1YyzsrtbzzPercent, last2YyzsrtbzzPercent, last3YyzsrtbzzPercent, last1JqjzcsylPercent, last2JqjzcsylPercent, last3JqjzcsylPercent, queryPage, redisMapKey, stockBaseInfo, zcfzl);
                    }
                });
            }
            redisUtil.expire(redisMapKey, 3, TimeUnit.DAYS);
            return null;
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        } finally {
            semapProBar.release();
        }
    }

    private boolean doCodeAnalyMethod(String queryDate, String code, double last1YyzsrtbzzPercent, double last2YyzsrtbzzPercent, double last3YyzsrtbzzPercent, double last1JqjzcsylPercent, double last2JqjzcsylPercent, double last3JqjzcsylPercent, EsQueryPageReq queryPage, String redisMapKey, StockBaseInfo stockBaseInfo, double paramZcfzl) {
        // 年报数据
        List<NewFinance> financeYearInfos = getNewFinancesEs(code, queryPage, 1);
        if (financeYearInfos.size() <= 3) return true;
        NewFinanceReturnInfo newFinanceReturnInfo = new NewFinanceReturnInfo();
        for (int i = 1; i <= 3; i++) {
            NewFinance newFinance = financeYearInfos.get(i - 1);
            double zcfzl = newFinance.getZcfzl();   //资产负债率
            // 最近1年的财务指标过滤
            if (i == 1) {
                double yyzsrtbzz = newFinance.getYyzsrtbzz();   //最近1次的营业总收入同比增长(%) >= 入参
                double jqjzcsyl = newFinance.getJqjzcsyl();     //最近1次的加权净资产收益率(%) ROE >= 入参
                double gsjlrtbzz = newFinance.getGsjlrtbzz();   //最近1次的归属净利润同比增长(%) > 0
                double kfjlrtbzz = newFinance.getKfjlrtbzz();   //最近1次扣非净利润同比增长(%)
                if (yyzsrtbzz >= last1YyzsrtbzzPercent && jqjzcsyl >= last1JqjzcsylPercent && zcfzl <= paramZcfzl && gsjlrtbzz > 0) {
                    newFinanceReturnInfo.setLast1YyzsrtbzzPercent(newFinance.getYyzsrtbzz());
                    newFinanceReturnInfo.setLast1Gsjlrtbzz(newFinance.getGsjlrtbzz());
                    newFinanceReturnInfo.setLast1Kfjlrtbzz(newFinance.getKfjlrtbzz());
                    newFinanceReturnInfo.setLast1Mll(newFinance.getMll());
                    newFinanceReturnInfo.setLast1Jll(newFinance.getJll());
                    newFinanceReturnInfo.setLast1Xsxjlyysr(newFinance.getXsxjlyysr());
                    newFinanceReturnInfo.setLast1JqjzcsylPercent(newFinance.getJqjzcsyl());
                } else {
                    return false;
                }
            }

            // 最近第2年的财务指标过滤
            if (i == 2) {
                double yyzsrtbzz = newFinance.getYyzsrtbzz();
                double jqjzcsyl = newFinance.getJqjzcsyl();
                double gsjlrgdhbzz = newFinance.getGsjlrtbzz();
                double kfjlrtbzz = newFinance.getKfjlrtbzz();
                if (yyzsrtbzz >= last2YyzsrtbzzPercent && jqjzcsyl >= last2JqjzcsylPercent && zcfzl <= paramZcfzl && gsjlrgdhbzz > 0) {
                    newFinanceReturnInfo.setLast2YyzsrtbzzPercent(newFinance.getYyzsrtbzz());
                    newFinanceReturnInfo.setLast2Gsjlrtbzz(newFinance.getGsjlrtbzz());
                    newFinanceReturnInfo.setLast2Kfjlrtbzz(newFinance.getKfjlrtbzz());
                    newFinanceReturnInfo.setLast2Mll(newFinance.getMll());
                    newFinanceReturnInfo.setLast2Jll(newFinance.getJll());
                    newFinanceReturnInfo.setLast2Xsxjlyysr(newFinance.getXsxjlyysr());
                    newFinanceReturnInfo.setLast2JqjzcsylPercent(newFinance.getJqjzcsyl());
                } else {
                    return false;
                }
            }


            // 最近第3年的财务指标过滤
            if (i == 3) {
                double yyzsrtbzz = newFinance.getYyzsrtbzz();
                double jqjzcsyl = newFinance.getJqjzcsyl();
                double gsjlrtbzz = newFinance.getGsjlrtbzz();
                double kfjlrtbzz = newFinance.getKfjlrtbzz();
                if (yyzsrtbzz >= last3YyzsrtbzzPercent && jqjzcsyl >= last3JqjzcsylPercent && zcfzl <= paramZcfzl) {
                    newFinanceReturnInfo.setLast3YyzsrtbzzPercent(newFinance.getYyzsrtbzz());
                    newFinanceReturnInfo.setLast3Gsjlrtbzz(newFinance.getGsjlrtbzz());
                    newFinanceReturnInfo.setLast3Kfjlrtbzz(newFinance.getKfjlrtbzz());
                    newFinanceReturnInfo.setLast3Mll(newFinance.getMll());
                    newFinanceReturnInfo.setLast3Jll(newFinance.getJll());
                    newFinanceReturnInfo.setLast3Xsxjlyysr(newFinance.getXsxjlyysr());
                    newFinanceReturnInfo.setLast3JqjzcsylPercent(newFinance.getJqjzcsyl());
                } else {
                    return false;
                }
            }
        }

        //营业总收入同比增长 当年大于第3年增长
        if (newFinanceReturnInfo.getLast1YyzsrtbzzPercent() >= newFinanceReturnInfo.getLast3YyzsrtbzzPercent() ||
                newFinanceReturnInfo.getLast1YyzsrtbzzPercent() >= 20) {

        } else {
            return false;
        }

        newFinanceReturnInfo.setCode(stockBaseInfo.getCode());
        newFinanceReturnInfo.setConceptName(stockBaseInfo.getConcept_name());
        newFinanceReturnInfo.setName(stockBaseInfo.getName());
        newFinanceReturnInfo.setGsld(stockBaseInfo.getGsld());
        newFinanceReturnInfo.setArea(stockBaseInfo.getArea());
        newFinanceReturnInfo.setIndustry(stockBaseInfo.getIndustry());
        newFinanceReturnInfo.setZxjj(stockBaseInfo.getZxjj());
        newFinanceReturnInfo.setZybl(stockBaseInfo.getZybl());

        // 按报告期（季度）财务数据
        List<NewFinance> financeRepInfos = getNewFinancesEs(code, queryPage, 0);
        int repSize = 3;
        if (financeRepInfos.size() >= 4) repSize = 4;
        for (int i = 1; i <= repSize; i++) {
            NewFinance newFinance = financeRepInfos.get(i - 1);
            // 最近（季度）的财务指标过滤，营业总收入同比增长、归属净利润同比增长必须是正数，资产负债率<60
            double yyzsrtbzz = newFinance.getYyzsrtbzz();
            double gsjlrtbzz = newFinance.getGsjlrtbzz();
            double zcfzl = newFinance.getZcfzl();
            if (yyzsrtbzz < 0 || gsjlrtbzz < 0 || zcfzl >= paramZcfzl) {
                return false;
            }
        }
        log.info("添加redismap值,key:{},code:{}", redisMapKey, code);
        redisUtil.hPut(redisMapKey, code, JSON.toJSONString(newFinanceReturnInfo));
        return true;
    }

    private List<NewFinance> getNewFinancesEs(String code, EsQueryPageReq queryPage, int type) {
        Pageable pageable = PageRequest.of(queryPage.getPageNum(), queryPage.getPageSize());
        BoolQueryBuilder bqb = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(code)) {
            bqb.must(QueryBuilders.matchPhraseQuery("code", code));
        }
        bqb.filter(QueryBuilders.termQuery("type", type));
        FieldSortBuilder sort = SortBuilders.fieldSort(StringUtils.isEmpty(queryPage.getSortName()) ? "dateMilli" : queryPage.getSortName()).unmappedType("integer").order(SortOrder.DESC);

        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        SearchQuery sq = queryBuilder.withQuery(bqb).withSort(sort).withPageable(pageable).build();
        return esNewFinanceDao.search(sq).getContent();
    }
}
